package top.xcxdl.rural.utils;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author : Ding
 * @create 2021/11/10 13:25
 */

import lombok.extern.log4j.Log4j2;
import org.springframework.util.StringUtils;
import top.xcxdl.rural.config.initexception.BusinessException;
import top.xcxdl.rural.config.initexception.ErrorEnum;
import top.xcxdl.rural.config.jwtconfig.JwtConfigProperties;
import top.xcxdl.rural.entity.BackAuthGroupRule;
import top.xcxdl.rural.entity.BackAuthRule;
import top.xcxdl.rural.service.IBackAdminService;
import top.xcxdl.rural.service.IBackAuthGroupRuleService;
import top.xcxdl.rural.service.IBackAuthGroupService;
import top.xcxdl.rural.service.IBackAuthRuleService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

@Component
@Log4j2
public class JwtInterceptor extends HandlerInterceptorAdapter {

    private static final ThreadLocal<String> INFO = new ThreadLocal<>();

    private static IBackAdminService adminService;

    private static IBackAuthGroupService groupService;

    private static IBackAuthRuleService ruleService;

    private final static String SUPER_ADMIN = "SUPER_ADMIN";

    private static Integer ERROR_CODE = null;

    @Resource
    private JwtConfigProperties jwtConfigProperties;

    @Resource
    public void setAdminService(IBackAdminService adminService) {
        JwtInterceptor.adminService = adminService;
    }

    @Resource
    public void setGroupService(IBackAuthGroupService groupService) {
        JwtInterceptor.groupService = groupService;
    }

    @Resource
    public void setRuleService(IBackAuthRuleService ruleService) {
        JwtInterceptor.ruleService = ruleService;
    }

    /**
     * 接口拦截器
     * @param request request
     * @param response response
     * @param handler handler
     * @return boolean
     * @throws Exception e
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 这里是个坑，因为带请求带headers时，ajax会发送两次请求，
        // 第一次会发送OPTIONS请求，第二次才会发生get/post请求，所以要放行OPTIONS请求
        // 如果是OPTIONS请求，让其响应一个 200状态码，说明可以正常访问

        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            // 放行OPTIONS请求
            return true;
        }

        // JwtConfigProperties通过@Autowired的值为null，所以需要通过以下方法获取bean
        if (jwtConfigProperties == null) {
            BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
            jwtConfigProperties = (JwtConfigProperties) factory.getBean("jwtConfigProperties");
        }

        boolean verify = false;

        // 获取header中的token --可能为空
        String tokenTemp = request.getHeader("Authorization");
        // 如果header中不存在token，则给予null，不做拆分，否则尝试拆分Bearer前缀
        String token = StringUtils.isEmpty(tokenTemp) ? null : tokenTemp.replace("Bearer ", "");

        // 验证token，如果验证失败就重定向到未授权（noAuthorization）页面
        try {
            verify = JWTUtils.verify(token, jwtConfigProperties);
        } catch (BusinessException e) {
            //这里的BusinessException是我自定义的异常，异常码位于ErrorEnum.java，用于抛出token验证失败的异常
            //验证失败，获取错误码
            ERROR_CODE = e.getCode();
        }

        if (!verify) {
            // token验证失败
            log.warn("请求因token被拦截，错误码{}，请求接口：{}", ERROR_CODE, request.getRequestURI());
            // 跳转到未授权页面
            redirectController(request, response);
            return false;
        }

        //执行到此，说明验证成功，在线程中存储当前登录用户的token...
        INFO.set(token);
        /*
          上面的都通过了，但是还需要进一步权限判断（后台权限过滤）

          下面这条判断语句是什么意思呢？
          如果是请求url前缀是"/admin"并且isPass方法返回true，就放行，否则就重定向到未登录页面
          "/admin"是后台的固定前缀，"/user"是C端固定前缀
          也就是说，只要请求接口不是"/admin"，就不会被二级拦截，理解了吧！
         */
        if (request.getRequestURI().startsWith("/admin") && !isPass(request.getRequestURI())) {
            //执行到这里，那么肯定是没有权限的，所以重定向到未授权（noAuthorization）页面
            redirectController(request, response);
            return false;
        }

        return true;
    }

    /**
     * 重要的二级放行方法，决定是否后台放行接口
     *
     * @param requestUri 请求的url
     * @return true放行，false不放行
     */
    public boolean isPass(String requestUri) {
        //根据token获取操作者类型（1管理员，0用户）
        if (JWTUtils.getAuthorType() != 1) {
            log.warn("用户不应当访问此后台接口，操作者ID：{}，操作者类型：{}，错误码：{}，请求接口：{}", JWTUtils.getAuthorId(), JWTUtils.getAuthorTypeName(), ErrorEnum.NO_AUTH.getErrorCode(), requestUri);
            ERROR_CODE = ErrorEnum.NO_AUTH.getErrorCode();
            return false;
        }
        //根据操作者id获取其对应角色id
        Integer gid = adminService.getById(JWTUtils.getAuthorId()).getAGroupId();
        //根据角色id获取角色码，进而判断是否是超级管理员，如果是，则直接放行，否则进入下方判断
        if (SUPER_ADMIN.equals(groupService.getById(gid).getRole())) {
            log.info("超级管理员（直接放行所有接口），操作者ID：{}，操作者类型：{}，请求接口：{}", JWTUtils.getAuthorId(), JWTUtils.getAuthorTypeName(), requestUri);
            return true;
        }
        /*
            二级放行（一级（全局）放行在webConfig）
            不需要权限拦截的接口
            加在这里可以直接跳过！！！！
         */
        if ("/admin/adminUser/getUserInfo".equals(requestUri)
                || "/admin/adminUser/getRuleRouters".equals(requestUri)
                || "/admin/adminUser/logout".equals(requestUri)
        ) {
            log.info("有权访问接口（此接口被二级放行），操作者ID：{}，操作者类型：{}，请求接口：{}", JWTUtils.getAuthorId(), JWTUtils.getAuthorTypeName(), requestUri);
            return true;
        }
        /*
            执行到此说明需要认证判断
         */
        if (
            //最高权限，只有超级管理员可以访问（特殊接口，不允许任何普通管理员访问）（默认是系统管理）
                (requestUri.startsWith("/admin/adminUser")
                        || requestUri.startsWith("/admin/adminGroup")
                        || requestUri.startsWith("/admin/adminRole")
                )) {
            ERROR_CODE = ErrorEnum.HIGHEST_AUTH.getErrorCode();
            log.warn("权限不足，此接口需要最高权限，操作者ID：{}，操作者类型：{}，错误码：{}，错误信息：{}，请求接口：{}", JWTUtils.getAuthorId(), JWTUtils.getAuthorTypeName(), ErrorEnum.HIGHEST_AUTH.getErrorCode(), ErrorEnum.HIGHEST_AUTH.getErrorMsg(), requestUri);
            return false;
        } else {
            /*
                普通权限，普通管理员的权限拦截 认证逻辑（查询数据库是否可以通行此请求。若能，返回通行路径）

                优化：2022年6月17日15:13:04
                此业务逻辑经重写，速度和可读性均得到优化，结合实际请求，如获取菜单的复杂接口，整体请求比之前访问时间平均缩短60ms（242ms -> 184ms）
             */
            String passPath = ruleService.isRequestExist(gid, requestUri);
            boolean isOk = !StringUtils.isEmpty(passPath);
            if (!isOk) {
                ERROR_CODE = ErrorEnum.NO_AUTH.getErrorCode();
                log.warn("无权访问接口，操作者ID：{}，操作者类型：{}，错误码：{}，错误信息：{}，请求接口：{}", JWTUtils.getAuthorId(), JWTUtils.getAuthorTypeName(), ErrorEnum.NO_AUTH.getErrorCode(), ErrorEnum.NO_AUTH.getErrorMsg(), requestUri);
            } else {
                log.info("有权访问接口，操作者ID：{}，操作者类型：{}，请求接口：{}，放行接口：{}", JWTUtils.getAuthorId(), JWTUtils.getAuthorTypeName(), requestUri, passPath);
            }
            return isOk;
        }
    }

    /**
     * 重定向到未登录并返回状态
     *
     * @param request  request
     * @param response response
     * @throws Exception 运行时异常
     */
    public void redirectController(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //设置跨域
        response.setHeader("Access-Control-Allow-Origin", "*");
        //发送重定向请求
        response.sendRedirect("/noAuthorization?code=" + ERROR_CODE);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //程序运行结束之后，删除线程
        INFO.remove();
    }

    public static String getTokenInfo() {
        return INFO.get();
    }
}